from selenium import webdriver
import time

from selenium.common import NoSuchElementException, TimeoutException
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.chrome.options import Options

"""
 准备工作
 通常来说，我们需要配合 Chrome 浏览器使用 Selenium，所以我们还需要额外安装下 ChromeDriver 和 Chrome 浏览器
 注意: 需要把下载的chromedriver.exe放置在python安装目录下
安装参考：
https://setup.scrape.center/chromedriver。
https://cuiqingcai.com/33043.html
https://wvghn4tr8fl.feishu.cn/docx/SQJvdY4hjoFgZAxv25LcFNFSnVv

还要安装selenium 三方库   这边用的版本号:  4.33.0
pip install selenium

"""

# 获取浏览器驱动
# browser = webdriver.Chrome()

"""
  示例1 执行百度搜索并获取 url、cookies、源码
"""
# try:
#     browser.get('https://www.baidu.com')
#     # 通过元素ID定位元素
#     input = browser.find_element(By.ID,'kw')
#     # input = browser.find_element(By.CSS_SELECTOR,'#kw')
#     # input = browser.find_element(By.XPATH,'//*[@id="kw"]')
#     # 输入框输入
#     input.send_keys('Python')
#     # 键盘按ENTER 搜索
#     input.send_keys(Keys.ENTER)
#     # 显示等待
#     wait = WebDriverWait(browser, 10)
#     # 等待元素出现，在往下执行
#     wait.until(EC.presence_of_element_located((By.ID, 'content_left')))
#     # 打印当前url
#     print(browser.current_url)
#     # 打印当前Cookies
#     print(browser.get_cookies())
#     # 打印源码
#     print(browser.page_source)
# finally:
#     # 关闭浏览器
#     browser.close()


"""
 其他查找元素的方法
 input = browser.find_element(By.CSS_SELECTOR,'#kw')  通过CSS选择器
 input = browser.find_element(By.XPATH,'//*[@id="kw"]')  通过XPATH选择器
 logo = browser.find_element(By.CLASS_NAME,'logo-image')  通过类名
 
 find_element 是获取单个节点
 find_elements 是获取多个节点  

"""

"""
  示例2  操作淘宝搜索、清空输入等
  节点交互  
  也就是说可以让浏览器模拟执行一些动作。比较常见的用法有：输入文字时用 send_keys 方法，清空文字时用 clear 方法，点击按钮时用 click 方法
"""

# try:
#     browser = webdriver.Chrome()
#     browser.get('https://www.taobao.com')
#     time.sleep(5)
#     input = browser.find_element(By.ID,'q')
#     input.send_keys('iPhone')
#     time.sleep(1)
#     input.clear()
#     input.send_keys('iPad')
#     button = browser.find_element(By.CLASS_NAME,'btn-search')
#     button.click()
# finally:
#     browser.close()



"""
  动作链
  另外一些操作，它们没有特定的执行对象，比如鼠标拖曳、键盘按键等，这些动作用另一种方式来执行，那就是动作链
  更多的动作链操作可以参考官方文档的动作链介绍：http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.common.action_chains。
"""

"""
 实例3 实现一个节点的拖曳操作，将某个节点从一处拖曳到另外一处
"""

# browser = webdriver.Chrome()
# url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
# browser.get(url)
# browser.switch_to.frame('iframeResult')
# # 拖拽元素
# source = browser.find_element(By.CSS_SELECTOR, '#draggable')
# # 目标元素
# target = browser.find_element(By.CSS_SELECTOR, '#droppable')
# actions = ActionChains(browser)
# actions.drag_and_drop(source, target)
# actions.perform()


"""
  示例4 获取节点信息
  Selenium 已经提供了选择节点的方法，返回的是 WebElement 类型，那么它也有相关的方法和属性来直接提取节点信息，如属性、文本等。
  
  常用属性方法和文本获取:
  get_attribute方法获取节点属性 
  text 属性，得到节点文本
  
  其他的一些属性
  id 属性可以获取节点 ID，location 属性可以获取该节点在页面中的相对位置，tag_name 属性可以获取标签名称，size 属性可以获取节点的大小，也就是宽高
"""

# browser = webdriver.Chrome()
# url = 'https://spa2.scrape.center/'
# browser.get(url)
# logo = browser.find_element(By.CLASS_NAME,'logo-image')
# logo2 = browser.find_element(By.CLASS_NAME,'logo-title')
# print(logo)
# print(logo.get_attribute('src'))
# print(logo2)
# print(logo2.text)
# print(logo2.id)
# print(logo2.location)
# print(logo2.tag_name)
# print(logo2.size)


"""
  示例5 切换Frame
  Selenium 打开页面后，它默认是在父级 Frame 里面操作，而此时如果页面中还有子 Frame，它是不能获取到子 Frame 里面的节点的。这时就需要使用 switch_to.frame 方法来切换 Frame
"""

# browser = webdriver.Chrome()
# url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
# browser.get(url)
# # 切换到子级的 frame
# browser.switch_to.frame('iframeResult')
# try:
#     logo = browser.find_element(By.CLASS_NAME,'logo')
# except NoSuchElementException:
#     print('NO LOGO')
# # 切换回父级
# browser.switch_to.parent_frame()
# logo = browser.find_element(By.CLASS_NAME,'logo')
# print(logo)
# print(logo.text)


"""
  延时等待
  在 Selenium 中，get 方法会在网页框架加载结束后结束执行，此时如果获取 page_source，可能并不是浏览器完全加载完成的页面，如果某些页面有额外的 Ajax 请求，我们在网页源代码中也不一定能成功获取到。
  所以，这里需要延时等待一定时间，确保节点已经加载出来。
  这里等待方式有两种：一种是隐式等待，一种是显式等待。
"""


"""
  示例6 等待的方式1 隐式等待
  当使用隐式等待执行测试的时候，如果 Selenium 没有在 DOM 中找到节点，将继续等待，超出设定时间后，则抛出找不到节点的异常。换句话说，当查找节点而节点并没有立即出现的时候，隐式等待将等待一段时间再查找 DOM，默认的时间是 0
"""

# browser = webdriver.Chrome()
# browser.implicitly_wait(10)
# browser.get('https://spa2.scrape.center/')
# input = browser.find_element(By.CLASS_NAME,'logo-image')
# print(input)


"""
  示例7 等待的方式2 显示等待
  隐式等待的效果其实并没有那么好，因为我们只规定了一个固定时间，而页面的加载时间会受到网络条件的影响。

  这里还有一种更合适的显式等待方法，它指定要查找的节点，然后指定一个最长等待时间。如果在规定时间内加载出来了这个节点，就返回查找的节点；如果到了规定时间依然没有加载出该节点，则抛出超时异常。示例如下：
"""


# browser = webdriver.Chrome()
# browser.get('https://www.taobao.com/')
# # 创建 WebDriverWait 这个对象，指定最长等待时间
# wait = WebDriverWait(browser, 10)
# # 在 10 秒内如果 ID 为 q 的节点（即搜索框）成功加载出来，就返回该节点；如果超过 10 秒还没有加载出来，就抛出异常
# input = wait.until(EC.presence_of_element_located((By.ID, 'q')))
# # 如果 10 秒内它是可点击的，也就是成功加载出来了，就返回这个按钮节点；如果超过 10 秒还不可点击，也就是没有加载出来，就抛出异常
# button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-search')))
# print(input, button)


"""
其他等待条件
title_is	标题是某内容
title_contains	标题包含某内容
presence_of_element_located	节点加载出来，传入定位元组，如 (By.ID, 'p')
visibility_of_element_located	节点可见，传入定位元组
visibility_of	可见，传入节点对象
presence_of_all_elements_located	所有节点加载出来
text_to_be_present_in_element	某个节点文本包含某文字
text_to_be_present_in_element_value	某个节点值包含某文字
frame_to_be_available_and_switch_to_it frame	加载并切换
invisibility_of_element_located	节点不可见
element_to_be_clickable	节点可点击
staleness_of	判断一个节点是否仍在 DOM，可判断页面是否已经刷新
element_to_be_selected	节点可选择，传入节点对象
element_located_to_be_selected	节点可选择，传入定位元组
element_selection_state_to_be	传入节点对象以及状态，相等返回 True，否则返回 False
element_located_selection_state_to_be	传入定位元组以及状态，相等返回 True，否则返回 False
alert_is_present	是否出现 Alert

更多等待条件的参数及用法介绍可以参考官方文档：http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.support.expected_conditions。
"""


"""
  示例 8 前进后退
  平常使用浏览器时，都有前进和后退功能，Selenium 也可以完成这个操作，它使用 back 方法后退，使用 forward 方法前进
"""

# 这里我们连续访问 3 个页面，然后调用 back 方法回到第二个页面，接下来再调用 forward 方法又可以前进到第三个页面。
# browser = webdriver.Chrome()
# browser.get('https://www.baidu.com/')
# browser.get('https://www.taobao.com/')
# browser.get('https://www.python.org/')
# browser.back()
# time.sleep(1)
# browser.forward()
# browser.close()



"""
 示例9 操作Cookies
 用 Selenium，还可以方便地对 Cookies 进行操作，例如获取、添加、删除 Cookies 等
"""

# browser = webdriver.Chrome()
# browser.get('https://www.zhihu.com/explore')
# print(browser.get_cookies())
# browser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'germey'})
# print(browser.get_cookies())
# browser.delete_all_cookies()
# print(browser.get_cookies())



"""
  示例10 选修卡管理
  在访问网页的时候，会开启一个个tab标签页。在 Selenium 中，我们也可以对选项卡进行操作
"""

# browser = webdriver.Chrome()
# browser.get('https://www.baidu.com')
# browser.execute_script('window.open()')
# #获取所有的tab
# print(browser.window_handles)
# # 根据索引切换tab
# browser.switch_to.window(browser.window_handles[1])
# browser.get('https://www.taobao.com')
# time.sleep(1)
# # 根据索引切换tab
# browser.switch_to.window(browser.window_handles[0])
# browser.get('https://python.org')


"""
 示例 11 异常处理
 在使用 Selenium 的过程中，难免会遇到一些异常，例如超时、节点未找到等错误，一旦出现此类错误，程序便不会继续运行了。这里我们可以使用 try except 语句来捕获各种异常。
"""

# browser = webdriver.Chrome()
# try:
#     browser.get('https://www.baidu.com')
# except TimeoutException:
#     print('Time Out')
# try:
#     browser.find_element(By.ID,'hello')
# except NoSuchElementException:
#     print('No Element')
# finally:
#     browser.close()


"""
  示例 12 反屏蔽
  现在很多网站都加上了对 Selenium 的检测，来防止一些爬虫的恶意爬取。即如果检测到有人在使用 Selenium 打开浏览器，那就直接屏蔽
  在大多数情况下，检测的基本原理是检测当前浏览器窗口下的 window.navigator 对象是否包含 webdriver 这个属性。因为在正常使用浏览器的情况下，这个属性是 undefined，
  然而一旦我们使用了 Selenium，Selenium 会给 window.navigator 设置 webdriver 属性。很多网站就通过 JavaScript 判断如果 webdriver 属性存在，那就直接屏蔽。

  这边有一个典型的案例网站：https://antispider1.scrape.center/，这个网站就使用了上述原理实现了 WebDriver 的检测，如果使用 Selenium 直接爬取的话，那就会返回如图所示的页面。
  
  时候我们可能想到直接使用 JavaScript 语句把这个 webdriver 属性置空，比如通过调用 execute_script 方法来执行如下代码：
  
  在 Selenium 中，我们可以使用 CDP（即 Chrome Devtools-Protocol，Chrome 开发工具协议）来解决这个问题，通过它我们可以实现在每个页面刚加载的时候执行 JavaScript 代码，
  执行的 CDP 方法叫作 Page.addScriptToEvaluateOnNewDocument，然后传入上文的 JavaScript 代码即可，这样我们就可以在每次页面加载之前将 webdriver 属性置空了。另外，我们还可以加入几个选项来隐藏 WebDriver 提示条和自动化扩展信息
  
  以上方法均可以实现 Selenium 反屏蔽。但对于一些特殊网站，如果它有更多的 WebDriver 特征检测，可能需要具体排查
"""


# options = Options()
# options.add_experimental_option("excludeSwitches", ["enable-automation"])
# options.add_experimental_option("useAutomationExtension", False)
#
# driver = webdriver.Chrome(options=options)
#
# driver.execute_cdp_cmd(
#     "Page.addScriptToEvaluateOnNewDocument",
#     {
#         "source": """
#             Object.defineProperty(navigator, 'webdriver', {
#                 get: () => undefined
#             })
#         """
#     },
# )
#
#
# driver.get("https://antispider1.scrape.center/")
# time.sleep(5)
# driver.close()

"""
 示例13 无头模式 
 Chrome 浏览器从 60 版本已经支持了无头模式，即 Headless。无头模式在运行的时候不会再弹出浏览器窗口，减少了干扰，而且它减少了一些资源的加载，如图片等，所以也在一定程度上节省了资源加载时间和网络带宽
"""

# option = Options()
# option.add_argument('--headless')
# browser = webdriver.Chrome(options=option)
# browser.set_window_size(1366, 768)
# browser.get('https://www.baidu.com')
# browser.get_screenshot_as_file('preview.png')
# browser.close()

